// Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
// SPDX-License-Identifier: MIT

use jpg_aigc_meta::{error_discriminant, read_aigc_meta, save_aigc_meta};
use std::ffi::{c_char, CStr, CString};
use std::os::raw::c_void;
use std::path::PathBuf;

/// Definition for the callback function that reports the result of an meta operation.
///
/// # Arguments
///
/// * `context` - A pointer to user-defined data, passed from the original function call.
/// * `error_code` - An integer indicating the status of the operation. `0` for success.
/// * `error_desc` - A C-string with a description of the error.
/// * `meta_content` - A C-string with the resulting metadata content, if successful.
///
/// # Safety
///
/// The `error_desc` and `meta_content` pointers are managed by Rust and must not be freed by the caller.
/// If the data is needed after the callback returns, it must be copied.
pub type MetaCallback = Option<
    extern "C" fn(
        context: *mut c_void,
        error_code: i64,
        error_desc: *const c_char,
        meta_content: *const c_char,
    ),
>;

fn c_str_to_string(c_str: *const c_char) -> Option<String> {
    if c_str.is_null() {
        return None;
    }
    unsafe { CStr::from_ptr(c_str).to_str().ok().map(String::from) }
}

/// Reads AIGC metadata from a JPEG file.
///
/// This function is synchronous and uses a callback to return the result.
///
/// # Arguments
///
/// * `asset_path` - A pointer to a null-terminated UTF-8 string representing the path to the source JPEG file.
/// * `context` - A pointer to user-defined data that will be passed to the callback.
/// * `callback` - A function pointer to be called with the result.
///
/// # Returns
///
/// Returns `true` if the operation was successfully initiated, `false` otherwise. The actual success or failure of the read operation is reported via the callback.
///
/// # Safety
///
/// The caller must ensure that `asset_path` is a valid pointer to a null-terminated UTF-8 encoded string.
/// The `context` pointer must also be valid. The pointers must be valid for the duration of the function call.
/// The caller is responsible for managing the memory of the pointers it passes to this function.
#[no_mangle]
pub extern "C" fn read_meta(
    asset_path: *const c_char,
    context: *mut c_void,
    callback: MetaCallback,
) -> bool {
    let callback = if let Some(callback) = callback {
        callback
    } else {
        return false;
    };

    let asset_path_str = if let Some(path) = c_str_to_string(asset_path) {
        path
    } else {
        return false;
    };
    if asset_path_str.is_empty() {
        return false;
    }

    let result = read_aigc_meta(&PathBuf::from(&asset_path_str));
    match result {
        Ok(label) => {
            let c_error_desc = CString::new("").unwrap();
            let c_label = CString::new(label).unwrap();
            callback(context, 0, c_error_desc.as_ptr(), c_label.as_ptr());
            true
        }
        Err(e) => {
            let c_error_desc = CString::new(e.to_string()).unwrap();
            let c_label = CString::new("").unwrap();
            callback(
                context,
                error_discriminant(&e),
                c_error_desc.as_ptr(),
                c_label.as_ptr(),
            );
            false
        }
    }
}

/// Writes AIGC metadata to a JPEG file.
///
/// This function is synchronous and uses a callback to return the result.
///
/// # Arguments
///
/// * `input_path` - A pointer to a null-terminated UTF-8 string representing the path to the source JPEG file.
/// * `output_path` - A pointer to a null-terminated UTF-8 string representing the path where the output JPEG file will be saved.
/// * `content_provider` - A pointer to a null-terminated UTF-8 string for the AIGC content_producer|content_propagator.
/// * `label` - A pointer to a null-terminated UTF-8 string for the AIGC label.
/// * `provider_id` - A pointer to a null-terminated UTF-8 string for the AIGC produce_id|propagate_id.
/// * `write_as_producer` - A boolean flag. If true, writes metadata as a producer; otherwise, as a propagator.
/// * `context` - A pointer to user-defined data that will be passed to the callback.
/// * `callback` - A function pointer to be called with the result.
///
/// # Returns
///
/// Returns `true` if the operation was successfully initiated, `false` otherwise. The actual success or failure of the write operation is reported via the callback.
///
/// # Safety
///
/// The caller must ensure that `input_path`, `output_path`, `content_provider`, `label`, and `provider_id` are valid pointers to null-terminated UTF-8 encoded strings.
/// The `context` pointer must also be valid. The pointers must be valid for the duration of the function call.
/// The caller is responsible for managing the memory of the pointers it passes to this function.
#[no_mangle]
pub unsafe extern "C" fn write_meta(
    input_path: *const c_char,
    output_path: *const c_char,
    content_provider: *const c_char,
    label: *const c_char,
    provider_id: *const c_char,
    write_as_producer: bool,
    context: *mut c_void,
    callback: MetaCallback,
) -> bool {
    let callback = if let Some(callback) = callback {
        callback
    } else {
        return false;
    };

    let input_path_str = if let Some(path) = c_str_to_string(input_path) {
        path
    } else {
        return false;
    };
    let output_path_str = if let Some(path) = c_str_to_string(output_path) {
        path
    } else {
        return false;
    };
    let content_provider_str = if let Some(s) = c_str_to_string(content_provider) {
        s
    } else {
        return false;
    };
    let label_str = if let Some(s) = c_str_to_string(label) {
        s
    } else {
        return false;
    };
    let provider_id_str = if let Some(s) = c_str_to_string(provider_id) {
        s
    } else {
        return false;
    };

    let source_path = PathBuf::from(input_path_str);
    if !source_path.exists() {
        return false;
    }

    let dest_path = PathBuf::from(output_path_str);
    let result = save_aigc_meta(
        &source_path,
        &dest_path,
        label_str,
        content_provider_str,
        provider_id_str,
        write_as_producer,
    );
    match result {
        Ok(label) => {
            let c_error_desc = CString::new("").unwrap();
            let c_label = CString::new(label).unwrap();
            callback(context, 0, c_error_desc.as_ptr(), c_label.as_ptr());
            true
        }
        Err(e) => {
            let c_error_desc = CString::new(e.to_string()).unwrap();
            let c_label = CString::new("").unwrap();
            callback(
                context,
                error_discriminant(&e),
                c_error_desc.as_ptr(),
                c_label.as_ptr(),
            );
            false
        }
    }
}
